Skip to main content

核心概念

Znode

Znode 基本概念

在 zookeeper 中,可以说 zookeeper 中的所有存储的数据是由 znode 组成的,节点也称为 znode,并以 key/value 形式存储数据

整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 / 开头。

进入 zookeeper 安装的 bin 目录,通过sh zkCli.sh打开命令行终端,执行 "ls /" 命令显示:

set /runoob 0
ls /
ls /zookeeper
ls /zookeeper/quota

基本操作

我们直观的看到此时存储的数据在根目录下存在 runoob 和 zookeeper 两个节点,zookeeper 节点下存在 quota 这个节点。

节点图

get -s /runoob
[zk: localhost:2181(CONNECTED) 3] get -s /runoob
0
cZxid = 0x100000008
ctime = Fri Oct 27 11:16:26 CST 2023
mZxid = 0x100000008
mtime = Fri Oct 27 11:16:26 CST 2023
pZxid = 0x100000008
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 1
numChildren = 0

其中第一行显示的 0 是该节点的 value 值。

Znode 的状态属性

您提供的信息可以整理成如下的表格:

属性描述
cZxid创建节点时的事务ID
ctime创建节点时的时间
mZxid最后修改节点时的事务ID
mtime最后修改节点时的时间
pZxid表示该节点的子节点列表最后一次修改的事务ID,添加子节点或删除子节点就会影响子节点列表,但是修改子节点的数据内容则不影响该ID(注意,只有子节点列表变更了才会变更pzxid,子节点内容变更不会影响pzxid)
cversion子节点版本号,子节点每次修改版本号加1
dataversion数据版本号,数据每次修改该版本号加1
aclversion权限版本号,权限每次修改该版本号加1
ephemeralOwner创建该临时节点的会话的sessionID。(如果该节点是持久节点,那么这个属性值为0)
dataLength该节点的数据长度
numChildren该节点拥有子节点的数量(只统计直接子节点的数量)

了解上面状态属性值,我们对 /runoob 节点做一次修改,执行命令 set /runoob 1 ,如所示:

set /runoob 1
[zk: localhost:2181(CONNECTED) 3] get -s /runoob                                [zk: localhost:2181(CONNECTED) 5] get -s /runoob
0 1
cZxid = 0x100000008 cZxid = 0x100000008
ctime = Fri Oct 27 11:16:26 CST 2023 ctime = Fri Oct 27 11:16:26 CST 2023
mZxid = 0x100000008 mZxid = 0x100000009
mtime = Fri Oct 27 11:16:26 CST 2023 mtime = Fri Oct 27 11:27:47 CST 2023
pZxid = 0x100000008 pZxid = 0x100000008
cversion = 0 cversion = 0
dataVersion = 0 dataVersion = 1
aclVersion = 0 aclVersion = 0
ephemeralOwner = 0x0 ephemeralOwner = 0x0
dataLength = 1 dataLength = 1
numChildren = 0 numChildren = 0

对比上面结果,可以看到 mZxid、mtime、dataVersion 都发生了变化。

在 /runoob 节点下,我们再添加一子节点,执行:

create  /runoob/child  0
get -s /runoob
[zk: localhost:2181(CONNECTED) 5] get -s /runoob                                [zk: localhost:2181(CONNECTED) 8] get -s /runoob
1 1
cZxid = 0x100000008 cZxid = 0x100000008
ctime = Fri Oct 27 11:16:26 CST 2023 ctime = Fri Oct 27 11:16:26 CST 2023
mZxid = 0x100000009 mZxid = 0x100000009
mtime = Fri Oct 27 11:27:47 CST 2023 mtime = Fri Oct 27 11:27:47 CST 2023
pZxid = 0x100000008 pZxid = 0x10000000a
cversion = 0 cversion = 1
dataVersion = 1 dataVersion = 1
aclVersion = 0 aclVersion = 0
ephemeralOwner = 0x0 ephemeralOwner = 0x0
dataLength = 1 dataLength = 1
numChildren = 0 numChildren = 1

可见 /runoob 节点的 pZxid、cversion、numChildren 都发生了相应的改变。

需要注意的是:zookeeper是为读多写少的场景设计的,Znode并不是用来存储大规模业务数据,而是用于存储少量的状态和配置信息,每个节点的数据最大不能超过1MB。

Watch

是注册在特点Znode上的触发器,当这个Znode发生改变,也就调用了create,delete,setData时,将会触发Znode上注册的对应事件,请求的Watch的客户端会接收到 <异步通知>。

具体交互过程:

  • 客户端调用getData方法,watch参数是true。服务端接到请求,返回节点数据,并且在对应的哈希表里插入被Watch的Znode路径,以及Watcher列表

watch监听机制

  • 当被Watch的Znode已删除,服务端会查找哈希表,找到该Znode对应的所有Watcher,异步通知客户端,并且删除哈希表中对应的Key-Value

watch监听机制

Zookeeper的一致性

概述

Zookeeper作为注册中心,通常将维护一个集群:

一致性

  • Zookeeper集群是一主多从结构,在更新数据时,首先更新到主节点(这里的节点指的是服务器,不是Znode),再同步到从节点,在读取数据时,直接读取任意从节点的数据,为了保证主从节点的数据一致性,zookeeper采用了ZAB协议,类似于一致性算法Paxos和Raft。

选举流程演示

  • SID:服务器ID。用来唯一标识一台ZooKeeper集群中的机器,每台机器不能重复,和myid一致。
  • ZXID:事务ID。ZXID是一个事务ID,用来标识一次服务器状态的变更。
  • Epoch:每个Leader任期的代号。没有Leader时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加。

选举流程

第一次选举

上面已经演示

非第一次选举

zookeeper集群正常启动之后,leader挂了之后的选举。而当一台机器进入Leader选举流程时,当前集群也可能会处于以下两种状态:

  • 集群中本来就已经存在一个Leader。

对于第一种已经存在Leader的情况,机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器来说,仅仅需要和Leader机器建立连接,并进行状态同步即可,自动变成 Follower 。

  • 集群中确实不存在Leader。

例如

SID为1、2、3的机器投票情况:

以下是您提供的数据以表格形式呈现:

(EPOCH,ZXID,SID )(EPOCH,ZXID,SID )(EPOCH,ZXID,SID )
(1,6,1)(1,6,2)(1,5,3)

选举Leader规则:

  • EPOCH大的直接胜出。
  • EPOCH相同,事务id大的胜出。
  • 事务id相同,服务器id大的胜出。

ZAB协议

ZAB即zookeeper Atomic Broadcast,用于解决zookeeper集群崩溃恢复,以及主从同步数据的问题,ZAB协议所定义的三种节点状态。

  • Looking:选举状态。
  • Following:从节点所处的状态。
  • Leading:主节点所处的状态。

最大ZXID的概念:最大ZXID也就是节点本地的最新事务编号,包含epoch和计数两部分。

假如zookeeper当前的主节点挂了,集群会进行崩溃恢复,ZAB的恢复分为三个阶段:

  • Leader election:选举阶段,此时集群的节点都处于Looking状态,他们会各自向其他节点发起投票,投票当中包含自己的服务器ID和最新的事务ZXID

    一致性

    • 接下来,节点会用自身的ZXID和从其他节点接收到的ZXID比较,如果发现别人的ZXID比自己大,也就是数据比自己新,那么就重新发起投票,把票投给目前已知最大的ZXID所属节点

    • 每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准Leader,状态变为Leading。其他节点的状态变为Following。

      一致性

  • Discovery:发现阶段,用于在从节点中发现最新的ZXID和事务日志。

    或许有人会问:既然Leader被选为主节点,已经是集群里数据最新的了,为什么还要从节点中寻找最新事务呢?这是为了防止某些意外情况,比如因网络原因在上一阶段产生多个Leader的情况。

    • 所以这一阶段,Leader集思广益,接收所有Follower发来各自的最新epoch值。Leader从中选出最大的epoch,基于此值加1,生成新的epoch分发给各个Follower。

    • 各个Follower收到全新的epoch后,返回ACK给Leader,带上各自最大的ZXID和历史事务日志。Leader选出最大的ZXID,并更新自身历史日志。

  • Synchronization:同步阶段,把Leader刚才收集得到的最新历史事务日志,同步给集群中的所有Follower。只有当半数Follower同步成功,这个准Leader才能真正成为Leader。

所以 epoch 的值是为了防止多个 Leader 出现的问题,自此,故障恢复正式完成。